/*
   Copyright (C) 2006
   Andrew Madigan

This file is part of nhttpd.

nhttpd is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2, or (at your option)
any later version.

nhttpd is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
General Public License for more details.

You should have received a copy of the GNU General Public License
along with nhttpd; see the file COPYING.  If not, write to the
Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
02110-1301 USA.

Linking this library statically or dynamically with other modules is
making a combined work based on this library.  Thus, the terms and
conditions of the GNU General Public License cover the whole
combination.

As a special exception, the copyright holders of this library give you
permission to link this library with independent modules to produce an
executable, regardless of the license terms of these independent
modules, and to copy and distribute the resulting executable under
terms of your choice, provided that you also meet, for each linked
independent module, the terms and conditions of the license of that
module.  An independent module is a module which is not derived from
or based on this library.  If you modify this library, you may extend
this exception to your version of the library, but you are not
obligated to do so.  If you do not wish to do so, delete this
exception statement from your version.
*/

#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netdb.h>
#include <pwd.h>
#include <grp.h>
#include <expat.h>
#include <string.h>
#include <netdb.h>
#include <stdbool.h>
#include <nhttpd/log.h>
#include "nhconfig.h"
#include "config.h"

#ifndef XML_BUF_SIZE
#define XML_BUF_SIZE 2048
#endif

#define ROOT_ELEMENT "nhttp-conf"
#define LIBDIR_ATTR "libdir"
#define ROOT_ATTR "root"
#define NAME_ATTR "name"
#define USER_ATTR "user"
#define GROUP_ATTR "group"
#define PRELOAD_ELEMENT "load-lib"
#define LIB_ATTR "lib"
#define SERVER_ELEMENT "server"
#define CONNECTOR_ELEMENT "connector"
#define MODE_ATTR  "mode"
#define PLAIN_VALUE "plain"
#define SSL_VALUE "ssl"
#define ADDR_ATTR "addr"
#define PORT_ATTR "port"
#define APPLICATION_ELEMENT "application"
#define MAIN_ATTR "main"
#define NAME_ATTR "name"
#define INIT_PARAMS_ELEMENT "init-params"
#define MAPPING_ELEMENT "mapping"
#define PATTERN_ATTR "pattern"

#define DEFAULT_PORT 80

#define CHECK_ERRORS(state) if (state->error >= 100) {\
	XML_StopParser(state->parser, false); return;}

#define streq(str1, str2) !strcmp(str1,str2)
#define parse_error(state, format, ...) {NH_log(LOG_CRIT,\
	"Parse error: " format "(%d:%d)", ##__VA_ARGS__,\
	XML_GetCurrentLineNumber(state->parser),\
	XML_GetCurrentColumnNumber(state->parser)); state->error++;}
#define attr_user(value, state, var, element) {\
		uidgid data =  NH_lookupUid(state, value);\
		state->var->uid = data.uid; state->var->gid=data.gid;}
#define attr_group(value, state, var, element) {\
		state->var->gid = NH_lookupGid(state, value);}
#define att_iterate(name, value, atts) if (atts[0] && atts[1]) \
{name = atts[0]; value = atts[1];} if (atts[0] && atts[1])\
for (int i = 0; atts[i]; i += 2, name = atts[i], value = atts[i + 1])

inline void NH_attsIter(const char*** atts, const char **attname, 
	const char** attvalue, bool incr) {
  if (incr) ++*atts;
  ++*atts;
  *attname = (*atts)[0];
  ++*atts;
  *attvalue = (*atts)[0];
}

typedef enum { DOC = 0, ROOT = 1, SERVER = 2, APP = 3 } CONFIG_STATE;

typedef struct	{
  XML_Parser parser;
  unsigned int error;
  CONFIG_STATE state;
  Configuration* self;
  Server* server;
  Application* app;
  int currentAppId;
  int errdepth;
} ConfigState;

typedef struct	{
  int uid;
  int gid;
} uidgid;

uidgid NH_lookupUid(ConfigState* state, const XML_Char* name)	{
  struct passwd* userinfo = getpwnam(name);
  uidgid rv;
  rv.uid = -1;
  rv.gid = -1;
  if (!userinfo)	{
	 parse_error(state, "User %s not found (%m)", name);
	 return rv;
  }
  rv.uid = userinfo->pw_uid;
  rv.gid = userinfo->pw_gid;
  return rv;
}

int NH_lookupGid(ConfigState* state, const XML_Char* name)	{
  struct group* groupinfo = getgrnam(name);
  if (!groupinfo)	{
	 parse_error(state, "Group %s not found (%m)", name);
	 return -1;
  }
  return groupinfo->gr_gid;
}

bool NH_lookupAddr(ConfigState* state, Connector* conn, 
						 const XML_Char* name)	{
  struct addrinfo* ainfo;
  getaddrinfo(name, NULL, NULL, &ainfo);

  if (!ainfo) {
	 parse_error(state, "Unable to find address for name %s (%m)", name);
	 return false;
  }
  
  conn->addrlen = ainfo->ai_addrlen;
  conn->addr = malloc(ainfo->ai_addrlen);
  memcpy(conn->addr, ainfo->ai_addr, ainfo->ai_addrlen);
  freeaddrinfo(ainfo);
  return true;
}
	

void NH_freeServer(void* _self)	{
  Server* self = _self;
  O_Free(self->mappings);
  O_Free(self->applications);
  O_Free(self->connectors);
  free(self);
}

Server* NH_newServer(ConfigState* state)	{
  static const Class ServerClass = {NH_freeServer};
	
  Server* self = malloc(sizeof(Server));
  self->super.methods = &ServerClass;
  self->libdir = state->self->libdir;
  self->root = state->self->root;
  self->name = "";
  self->uid = state->self->uid;
  self->gid = state->self->gid;
  self->serverVersion = state->self->serverVersion;
  self->initParams = HT_Create(true, true);
  self->mappings = L_Create();
  self->applications = L_Create();
  self->connectors = L_Create();
  L_Add(state->self->servers, self);
	
  return self;
}

void NH_freeConnector(void* restrict _self)	{
  Connector* self = _self;
  free(self->addr);
  free(self);
}

Connector* NH_newConnector(ConfigState* state)	{
  static Class ConnectorClass = {NH_freeConnector};
	
  Connector* self = malloc(sizeof(Connector));
  self->super.methods = &ConnectorClass;
  self->mode = MODE_PLAIN;
  self->parent = state->server;

  self->addr = malloc(sizeof(struct sockaddr_in));
  struct sockaddr_in* addr = (struct sockaddr_in*) self->addr;
	addr->sin_family = AF_INET;
	addr->sin_addr.s_addr = INADDR_ANY;
	addr->sin_port = htons(DEFAULT_PORT);
	self->addrlen = sizeof(struct sockaddr_in);
	
  L_Add(state->self->connectors, self);
  L_Add(state->server->connectors, self);
	
  return self;
}

void NH_freeApplication(void* _self) {
  Application* self = _self;
  free(self->modlib);
  free(self->main);
  free(self->name);
  O_Free(self->initParams);
  free(self);
  //NOTE: The servlet is expected to already be freed
}

Application* NH_newApplication(ConfigState* state) {
  static Class ApplicationClass = {NH_freeApplication};

  Application* self = malloc(sizeof(Application));
  self->super.methods = &ApplicationClass;
  self->server = state->server;
  self->name = "";
  self->modlib = NULL;
  self->main = "CreateServlet";
  self->initParams = HT_Create(true, true);
  self->servlet = NULL;
  self->uid = state->server->uid;
  self->gid = state->server->gid;
  self->id = state->currentAppId++;

  state->app = self;
  L_Add(state->server->applications, self);
  return self;
}

void NH_freeMapping(void* _self) {
  Mapping* self = _self;
  regfree(self->compiled);
  free(self->pattern);
  free(self);
}

Mapping* NH_newMapping(ConfigState* state) {
  static Class MappingClass = {NH_freeMapping};

  Mapping* self = malloc(sizeof(Mapping));
  self->super.methods = &MappingClass;
  self->pattern = NULL;
  self->app = state->app;
  self->compiled = NULL;
  L_Add(state->server->mappings, self);
  return self;
}

void XMLCALL NH_configElementHandler(void* _state, const XML_Char *name, 
									  const XML_Char **atts)	{
  ConfigState* state = _state;
  CHECK_ERRORS(state);
  const char *attname, *value;
  if (state->errdepth) {
	 state->errdepth++;
	 return;
  }
  switch (state->state)	{
  case DOC:
	 if (!streq(name, ROOT_ELEMENT))	{
		parse_error(state, "Bad root element in configuration :%s", name);
	 }
	 else	{
		state->state++;
		att_iterate(attname, value, atts) {
		  if (streq(attname, LIBDIR_ATTR))	{
			 free(state->self->libdir);
			 state->self->libdir = strdup(value);
		  }
		  else if (streq(attname, ROOT_ATTR))	{
			 free(state->self->root);
			 state->self->root = strdup(value);
		  }
		  else if (streq(attname, USER_ATTR)) {attr_user(value, state, self, 
		  ROOT_ELEMENT)}
		  else if (streq(attname, USER_ATTR)) {attr_group(value, state, self, 
		  ROOT_ELEMENT)}
		  else	{
			 parse_error(state, "Invalid attribute: %s", attname);
		  }
		}
	 }
	 break;
  case ROOT:
	 if (streq(name, PRELOAD_ELEMENT))	{
		bool hadlib = false;
		att_iterate(attname, value, atts) {
		  if (!hadlib && streq(attname, LIB_ATTR))	{
			 if (L_Contains(state->self->libs, 
								 value, L_strcmp))	{
				parse_error(state, "Repeated Library: %s",
								value);
			 }
			 else L_Add(state->self->libs, strdup(value));
			 hadlib = true;
		  }
		  else	{
			 parse_error(state, "Invalid (or repeated) attribute: %s",
							 attname);
		  }
		}
		if (!hadlib)
		  parse_error(state, "No lib specified by load-lib declaration");
	 }
	 else if (streq(name, SERVER_ELEMENT))	{
		state->state++;
		state->server = NH_newServer(state);
		att_iterate(attname, value, atts) {
		  if (streq(attname, LIBDIR_ATTR))	{
			 state->server->libdir = strdup(value);
		  }
		  else if (streq(attname, ROOT_ATTR))	{
			 state->server->root = strdup(value);
		  }
		  else if (streq(attname, NAME_ATTR))	{
			 state->server->name = strdup(value);
		  }
		  else if (streq(attname, USER_ATTR)) {attr_user(value, state, server, 
		  ROOT_ELEMENT)}
		  else if (streq(attname, GROUP_ATTR)) {attr_group(value, state, server, 
		  ROOT_ELEMENT)}
		  else	{
			 parse_error(state, "Invalid attribute: %s", attname);
		  }
		}
	 }
	 else	{
		parse_error(state, "Invalid tag: %s", name);
	 }
	 break;
  case SERVER:
	 if (streq(name, CONNECTOR_ELEMENT))	{
		Connector* conn = NH_newConnector(state);
		int port = -1;
		
		att_iterate(attname,value,atts)	{
		  if (streq(attname, MODE_ATTR))	{
			 //This is the default
			 if (streq(value, PLAIN_VALUE)); 
			 else if (streq(value, SSL_VALUE))	{
				conn->mode = MODE_SSL;
			 }
			 else	{
				parse_error(state, "Mode not supported: %s",
								value);
			 }
		  }
		  else if (streq(attname, ADDR_ATTR))	{
			 NH_lookupAddr(state, conn, value);
		  }
		  else if (streq(attname, PORT_ATTR)) {
			 port = atoi(value);
		  }
		  else if (streq(attname, NAME_ATTR))	{
			 state->server->name = strdup(value);
		  }
		  else if (streq(attname, USER_ATTR)) {attr_user(value, state, server, 
		  ROOT_ELEMENT)}
		  else if (streq(attname, GROUP_ATTR)) {attr_group(value, state, server, 
		  ROOT_ELEMENT)}
		  else	{
			 parse_error(state, "Invalid attribute: %s", attname);
		  }
		}

		//NOTE: This assumes that the struct for ipv6 has the port in the same
		//spot as ipv4.
		if (port != -1) {
		  struct sockaddr_in* addr = (struct sockaddr_in*) conn->addr;
		  addr->sin_port = htons(port);
		}
	 }
	 else if (streq(name, APPLICATION_ELEMENT)) {
		state->state++;
		Application* app = NH_newApplication(state);

		att_iterate(attname,value,atts) {
		  if (streq(attname, MAIN_ATTR)) {
			 app->main = strdup(value);
		  }
		  else if (streq(attname, NAME_ATTR)) {
			 app->name = strdup(value);
		  }
		  else if (streq(attname, LIB_ATTR)) {
			  String* modlibval = STR_CreateCStr(state->server->libdir);
			  STR_Append(modlibval, '/');
			  STR_AppendCStr(modlibval, value);
			 app->modlib = STR_ToCStrFree(modlibval);
		  }
		  else if (streq(attname, USER_ATTR)) {attr_user(value, state, app, 
		  APPLICATION_ELEMENT)}
		  else if (streq(attname, GROUP_ATTR)) {attr_group(value, state, app, 
		  APPLICATION_ELEMENT)}
		  else
			 parse_error(state, "Invalid attribute: %s", attname);
		}
	 }
	 break;
  case APP:
	 if (streq(name, INIT_PARAMS_ELEMENT)) {
		att_iterate(attname,value,atts) {
		  HT_Put(state->app->initParams, strdup(attname),
					strdup(value));
		}
	 }
	 else if (streq(name, MAPPING_ELEMENT)) {
		Mapping* mapping = NH_newMapping(state);
		att_iterate(attname,value,atts) {
		  if (streq(attname, PATTERN_ATTR)) {
			 mapping->pattern = strdup(value);
		  }
		  else {
			 parse_error(state, "Invalid attribute: %s", attname);
		  }
		}
		if (mapping->pattern) 
			regcomp(mapping->compiled, mapping->pattern, REG_ICASE | REG_NOSUB);
	 }
  }
}

void XMLCALL NH_configEndElementHandler(void* _state, const XML_Char* name) {
  ConfigState* state = _state;
  CHECK_ERRORS(state);
  if (state->errdepth) {
	 state->errdepth--;
	 return;
  }
  switch (state->state) {
  case DOC:
  case ROOT: break;
  case SERVER:
	 if (streq(name, SERVER_ELEMENT)) {
		state->server = NULL;
		state->state = DOC;
	 }
	 break;
  case APP:
	 if (streq(name, APPLICATION_ELEMENT)) {
		state->app = NULL;
		state->state = SERVER;
	 }
  }
}

void NH_freeConfiguration(void* _self) {
  Configuration* self = _self;

  free(self->libdir);
  free(self->root);
  O_Free(self->libs);
  O_Free(self->servers);
  O_Free(self->connectors);
  O_Free(self->application);

  free(self);
}

Configuration* NH_newConfiguration() {
  static Class ConfigurationClass = {NH_freeConfiguration};
  Configuration* self = malloc(sizeof(Configuration));
  self->super.methods = &ConfigurationClass;
#ifdef NH_CONFIG_MODDIR
	self->libdir = strdup(NH_CONFIG_MODDIR);
#elif defined(NH_DEFMODDIR)
  self->libdir = strdup(NH_DEFMODDIR);
#else 
	self->libdir = strdup(".");
#endif
  self->root = strdup(".");
  self->uid = getuid();
  self->gid = getgid();
  String* version = STR_CreateCStr(PACKAGE_NAME);
  STR_Append(version, '/');
  STR_AppendCStr(version, PACKAGE_VERSION);
  self->serverVersion = STR_ToCStrFree(version);
  self->libs = L_Create();
  self->servers = L_Create();
  self->connectors = L_Create();
  self->application = L_Create();

  return self;
}

Configuration* NH_readConfig(int fd) {
  XML_Parser parser = XML_ParserCreate(NULL);

  ConfigState state = {parser, 0, DOC, NH_newConfiguration(), NULL, NULL,0,0};

  XML_SetUserData(state.parser, &state);
  XML_SetElementHandler(state.parser, NH_configElementHandler, 
	  NH_configEndElementHandler);

  bool error = false;

  while (true) {
	 int bytes_read;
	 void *buff = XML_GetBuffer(state.parser, XML_BUF_SIZE);
	 if (buff == NULL) {
		NH_log(LOG_ERR, "Unable to allocate buffer for XML Parsing");
		error = true;
		break;
	 }

	 bytes_read = read(fd, buff, XML_BUF_SIZE);
	 if (bytes_read < 0) {
		NH_log(LOG_ERR, "I/O Error while reading configuration: %m");
		error = true;
		break;
	 }

	 if (!XML_ParseBuffer(state.parser, bytes_read, bytes_read == 0)) {
		const char* err;
		parse_error((&state), "Syntax Error: %s", err = 
			XML_ErrorString(XML_GetErrorCode(state.parser)));
		error = true;
		break;
	 }

	 if (bytes_read == 0)
		break;
  }

  XML_ParserFree(state.parser);

  if (state.error) {
	 NH_log(LOG_ERR, "%d errors in configuration", state.error);
	 error = true;
  }
  if (error) {
	 O_Free(state.self);
	 return NULL;
  }
  return state.self;
}

int NH_matchesMapping(char* uri, Mapping* mapping) {
	if (mapping->pattern == NULL)
		return 0;
	return regexec(mapping->compiled, uri, 0, NULL, 0) == 0 ? 1 : -1;
}

#ifdef CONFIG_TEST
int main(int argc, char* argv[]) {
  NH_openlog(true, argv[0], LOG_CONS, LOG_USER);
  Configuration* config = NH_readConfig(0);

  if (!config) {
	 printf("Config Error!\n");
	 return -1;
  }

  printf("nhttp configuration\n");
  printf("\tlibdir: %s\n", config->libdir);
  printf("\troot: %s\n", config->root);
  printf("\tuid: %d\n", config->uid);
  printf("\tgid: %d\n", config->gid);
  printf("\n\tLibraries\n");
  ListIter* iter = LI_Create(config->libs);
  while (LI_Next(iter)) {
	 printf("\t\t%s\n", (char*) LI_GetValue(iter));
  }
  O_Free(iter);
  printf("\n\tServers\n");
  iter = LI_Create(config->servers);
  Server* server;
  while (LI_Next(iter)) {
	 server = LI_GetValue(iter);
	 printf("\t\tServer %s\n", server->name);
	 printf("\t\t\tlibdir: %s\n", server->libdir);
	 printf("\t\t\troot: %s\n", server->root);
	 printf("\t\t\tname: %s\n", server->name);
	 printf("\t\t\tuid: %d\n", server->uid);
	 printf("\t\t\tgid: %d\n", server->gid);

	 printf("\n\t\t\tApplications\n");
	 ListIter* iter2 = LI_Create(server->applications);
	 Application* app;
	 while (LI_Next(iter2)) {
		app = LI_GetValue(iter2);
		printf("\t\t\t\tApplication %s\n", app->name);
		if (app->modlib)
		  printf("\t\t\t\tlibrary: %s\n", app->modlib);
		else
		  printf("\t\t\t\tinternal application\n");
		printf("\t\t\t\tmain: %s\n", app->main);
		printf("\t\t\t\tname: %s\n", app->name);
		printf("\t\t\t\tuid: %d\n", app->uid);
		printf("\t\t\t\tgid: %d\n", app->gid);

		printf("\n\t\t\t\tInit Params\n");
		HT_Iterator* params = HTI_Create(app->initParams);
		while (HTI_Next(params)) {
		  printf("\t\t\t\t\t%s = %s\n", (char*) HTI_GetKey(params),
					(char*) HTI_GetValue(params));
		}
		O_Free(params);
	 }
	 O_Free(iter2);

	 printf("\n\t\t\tMappings\n");
	 iter2 = LI_Create(server->mappings);
	 Mapping* mapping;
	 while (LI_Next(iter2)) {
		mapping = LI_GetValue(iter2);
		if (mapping->pattern == NULL) {
		  printf("\t\t\t\tDefault Mapping: %s\n", mapping->app->name);
		}
		else {
		  printf("\t\t\t\t%s -> %s\n", mapping->pattern, mapping->app->name);
		}
	 }
	 O_Free(iter2);

	 printf("\n\t\t\tConnectors\n");
	 iter2 = LI_Create(server->connectors);
	 Connector* conn;
	 while (LI_Next(iter2)) {
		conn = LI_GetValue(iter2);
		printf("\t\t\t\tConnector %s\n", conn->name);
		if (conn->mode == MODE_SSL) printf("\t\t\t\t\tMode: ssl\n");
		else printf("\t\t\t\t\tMode: plain\n");
	 }
	 O_Free(iter2);
  }
}
#endif
